One|Many to Many
Foreign key
One difference from programming language and database table is:
programming language supports structure definition while a column in table is alway flat.
You can basically do this:
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string? Synopsis { get; set; }
public Genre Genre { get; set; } // translate to foreign key
}
public class Genre
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
2
3
4
5
6
7
8
9
10
11
12
13
On the one side, you can nest a type inside the entity mode, while in database the genre should simply be a foreign key points to the primary key of table of Genre
.
Actually that's exactly the default entity framework will do for us:
- Auto generate a field named
<prop_name>Id
in table of the containing entity mode targets. - Link
<prop_name>Id
as foreign key to the primary key of another table.
On the many side, a genre can match many movies, so a collection type can be nested inside the entity model.
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string? Synopsis { get; set; }
public Genre Genre { get; set; } // translate to foreign key
}
public class Genre
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public ICollection<Movie> Movies { get; set; } = new HashSet<Movie>(); // [!code highlight]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Custom foreign key
class MovieMapper : IEntityTypeConfiguration<Movie>
{
public void Configure(EntityTypeBuilder<Movie> builder)
{
builder.HasOne(movie => movie.Genre) // pick entity instance to reference as foreign
.WithMany(genre => genre.Movies) // which colletion should be the many
.HasPrincipalKey(genre => genre.Id) // how to distinct the foreign
.HasForeignKey(movie => movie.MainGenreId); // which prop should be foreign key
}
}
2
3
4
5
6
7
8
9
10
By doing so, entity framework maps foreign key as MainGenreId
in Movie
table while it references from the Genre
table.
Fetching many
To fetch nested property in entity framework core, you'll have to explicitly include it in you query. In the days of old entity framework all nested properties are fetched by default which affects the performance.
[HttpGet("{id:int}")]
[ProducesResponseType(typeof(Movie), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Get([FromRoute] int id)
{
var movie = await _context.Movies
.Include(movie => movie.Genre) // [!code highlight]
.SingleOrDefaultAsync();
return movie == null
? NotFound()
: Ok(movie);
}
2
3
4
5
6
7
8
9
10
11
12
13
Prevent cycle deserilization
However, if the nested property has a cycle reference inside, we should apply JsonIgnore
upon.
using System.Text.Json.Serialization;
public class Genre
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
[JsonIgnore] // cycle happens here!
public ICollection<Movie> Movies { get; set; } = new HashSet<Movie>();
}
2
3
4
5
6
7
8
9